home *** CD-ROM | disk | FTP | other *** search
/ Mac-Source 1994 July / Mac-Source_July_1994.iso / C and C++ / Science⁄Math / VideoToolbox / VideoToolboxSources / Timer.c < prev    next >
Text File  |  1993-07-09  |  10KB  |  281 lines

  1. /*
  2. Timer.c
  3. An interval timer based on Apple's Time Manager. It returns the time that
  4. elapsed between calling StartTimer() and StopTimer(). Under System 7, which has
  5. the Extended Time Manager, the timing is very accurate (better than 1 part in
  6. 1000) and a precision of better than 200 µs. Under System 6 the precision will
  7. be about 1 ms, and the returned times will tend to be about 10% low due to
  8. documented deficiencies of Apple's old Revised Time Manager. The even older
  9. "Standard" Time Manager is not supported here, and will result in an error message.
  10.  
  11. One could easily add a PeekTimer() routine to this set, but I haven't
  12. gotten around to it. Send it to me if you write it.
  13.  
  14. Timer *NewTimer(void);
  15. void DisposeTimer(Timer *t);
  16. void StartTimer(Timer *t);
  17. long StopTimer(Timer *t);                    // µs, up to 36 minutes
  18. double StopTimerSecs(Timer *t);                // s, µs precision and no time limit
  19.  
  20.     Timer *timer;
  21.     long t;
  22.     
  23.     timer=NewTimer();
  24.     StartTimer(timer);
  25.     do(i=0;i<100;i++);
  26.     t=StopTimer(timer);
  27.     DisposeTimer(timer);
  28.     printf("One hundred iterations takes %ld µs\n",t);
  29.  
  30. The timing result comes in two flavors. StopTimer() returns the time in
  31. microseconds as a long, which can hold a time up to 2,147,483,647 µs, which is
  32. nearly 36 minutes. StopTimerSecs() returns the time in secs as a double, and can
  33. time an interval of essentially unlimited duration with microsecond precision.
  34.  
  35. You can have many Timers running at once. The only restriction is that you must
  36. create each Timer, by calling NewTimer() before you use it, and, obviously,
  37. should not use it after calling DisposeTimer().
  38.  
  39. The Time Manager seems to use interrupts at a higher rate than the 10 s interval
  40. I requested here (which would never expire in typical use). I infer this from
  41. the fact that disabling interrupts by SetPriority(7) greatly reduces the timing
  42. value returned by StopTimer(). So don't disable interrupts while you're timing.
  43.  
  44. StopTimer() adds a small offset (about 62 µs on a Mac IIci) to the raw time in
  45. order to return an unbiased estimate of the time from when StartTime was called
  46. to when StopTime() was called. This is the most useful time measure for
  47. measuring the interval between two events (e.g. video frames). Alternatively, if
  48. you wish to measure processing time then it is more useful to compute the time
  49. from when StartTimer() returns to when StopTimer() is called. For this you
  50. simply subtract the fixed duration of StartTimer(), call-to-return, which is
  51. provided in µs in your Timer structure in the long structure member
  52. "timeToStartTimer".
  53.  
  54.     betweenTime = StopTimer(t) - t->timeToStartTimer;
  55.     
  56. If you use StopTimerSecs() instead you'll have to divide timeToStartTimer by a
  57. million to convert µs to s. For some purposes this offset is negligibly
  58. small. On my Mac IIci the timeToStartTimer is 240 µs. This offset, and the one
  59. mentioned above are measured automatically the first time you call NewTimer().
  60. Alternatively, instead of having to remember the name of the structure member,
  61. just measure the timeToStartTimer yourself and subtract it from all subsequent
  62. measures:
  63.  
  64.     StartTimer(t);
  65.     s0=StopTimerSec(t);
  66.  
  67. The Timer structures are kept in a linked list so that KillEveryTimer(), which
  68. is placed in the _atexit() queue, can find and kill them when the application
  69. exits.
  70.  
  71. HISTORY:
  72. 8/19/92 dgp    based on Time Manager chapter in Inside Mac VI and TimeIt.c, 
  73.     which is now obsolete. I also benefited from examining code by Jonothan Kolodny
  74.     forwarded to me by Thomas Busey.
  75. 8/27/92    dgp    Rewrote everything. Made the interrupt service routine reentrant 
  76.     by eliminating all use of global variables, using only the structure pointed
  77.     to by A1. There can now be an unlimited number of timers active at once.
  78.     Added NewTimer() and DisposeTimer() to manage them. 
  79. 9/10/92    dgp    added calls to VM to HoldMemory() and UnHoldMemory(). According to Apple's
  80.     Memory book this isn't strictly necessary, since Time Manager tasks will be
  81.     called only when it's safe.
  82. 1/11/93 dgp StopTimerSecs() now returns NAN if called with a NULL pointer.
  83. 7/9/93    dgp    Test MATLAB in if() instead of #if. 
  84. */
  85. #include "VideoToolbox.h"
  86. static pascal void TimerTask(void);
  87. void KillEveryTimer(void);
  88. Ptr GetA1(void);
  89.  
  90. /* This is a copy for reference. Original is in VideoToolbox.h.
  91. struct Timer{
  92.     TMTask time;
  93.     long ourA5;
  94.     long interval,elapsed,elapsedIntervals;
  95.     long timeToStartTimer;            // minimum time in µs
  96.     long stopDelay;                    // µs from call to stop, re from call to start
  97.     long timeManagerVersion;
  98.     struct Timer *next,*previous;    // doubly linked list of Timers
  99. };
  100. */
  101.  
  102. static Timer defaultTimer,qTimer={0,0,0,0,0,0,0,0,0,0,0};
  103. static long vmPresent=0;
  104. #define TASK_SIZE 1000    // Generous guess for size of routine
  105.  
  106. Timer *NewTimer(void)
  107. {
  108.     static short firstTime=1;
  109.     extern Timer defaultTimer,qTimer;
  110.     Timer *t,*tt;
  111.     long j;
  112.     
  113.     if(firstTime){
  114.         firstTime=0;
  115.         qTimer.next=qTimer.previous=NULL;
  116.         if(!MATLAB)_atexit(KillEveryTimer);
  117.         Gestalt(gestaltVMAttr,&vmPresent);
  118.         vmPresent &= gestaltVMPresent;
  119.         t=&defaultTimer;
  120.         t->ourA5 = SetCurrentA5();
  121.         t->time.tmAddr = (TimerProcPtr)TimerTask;
  122.         t->time.tmCount=t->time.tmWakeUp=t->time.tmReserved=0;
  123.         t->elapsedIntervals=t->elapsed=0;                            
  124.         t->timeManagerVersion=0;
  125.         Gestalt(gestaltTimeMgrVersion,&t->timeManagerVersion);
  126.         switch(t->timeManagerVersion){
  127.         case 0:
  128.         case gestaltStandardTimeMgr:
  129.             printf("NewTimer: old System lacks the Revised Time Manager.\n");
  130.             return NULL;
  131.         case gestaltRevisedTimeMgr:
  132.             t->interval = 1L;                // Set the timer interval to 1 ms
  133.             break;
  134.         case gestaltExtendedTimeMgr:
  135.         default:
  136.             t->interval = -10000000L;        // Set the timer interval to 10 s
  137.         }
  138.         t->next=NULL;
  139.         t->previous=&qTimer;
  140.         t->timeToStartTimer=t->stopDelay=0;
  141.         
  142.         // Measure timeToStartTimer and stopDelay offsets.
  143.         t=NewTimer();
  144.         StartTimer(t);
  145.         t->timeToStartTimer=StopTimer(t);
  146.         tt=NewTimer();
  147.         StartTimer(t);
  148.         StartTimer(tt);
  149.         j=StopTimer(t);
  150.         defaultTimer.stopDelay=t->stopDelay=2*t->timeToStartTimer-j;
  151.         // The computed "cycle" interval will have stopDelay removed.
  152.         // The user wishing to compute the "between" interval will be
  153.         // subtracting the timeToStartTimer, so we should subtract 
  154.         // the stopDelay from that
  155.         // so the stopDelay cancels out when "between" time is computed.
  156.         defaultTimer.timeToStartTimer=t->timeToStartTimer-=t->stopDelay;
  157.         DisposeTimer(tt);
  158.         return t;
  159.     }
  160.     t=(Timer *)NewPtr(sizeof(Timer));
  161.     if(t!=NULL){
  162.         *t=defaultTimer;
  163.         t->next=qTimer.next;
  164.         if(t->next!=NULL)t->next->previous=t;
  165.         qTimer.next=t;
  166.         if(vmPresent){
  167.             HoldMemory(t,sizeof(*t));
  168.             HoldMemory(t->time.tmAddr,TASK_SIZE);
  169.         }
  170.         if(t->timeManagerVersion==gestaltRevisedTimeMgr)InsTime((QElemPtr)t);
  171.         else InsXTime((QElemPtr)t);
  172.     }
  173.     return t;
  174. }
  175.  
  176. void DisposeTimer(Timer *t)
  177. {
  178.     Timer *tt;
  179.     
  180.     if(t==NULL)return;
  181.     RmvTime((QElemPtr)t);
  182.     if(vmPresent){
  183.         UnholdMemory(t,sizeof(*t));
  184.         UnholdMemory(t->time.tmAddr,TASK_SIZE);
  185.     }
  186.     t->previous->next=t->next;
  187.     if(t->next!=NULL)t->next->previous=t->previous;
  188.     DisposPtr((Ptr)t);
  189. }
  190.  
  191. void StartTimer(Timer *t)
  192. {
  193.     if(t==NULL)return;
  194.     PrimeTime((QElemPtr)t,t->interval);
  195. }
  196.  
  197. long StopTimer(Timer *t)                            // Returns µs
  198. {
  199.     register long microSeconds;
  200.     extern Timer defaultTimer;
  201.  
  202.     if(t==NULL)return 0;
  203.     RmvTime((QElemPtr)t);
  204.     // add up the elapsed intervals plus the one we're in, minus the time left
  205.     microSeconds=t->elapsed + t->interval;
  206.     if(microSeconds>0) microSeconds*=1000;            // convert ms to µs
  207.     else microSeconds=-microSeconds;
  208.     if(t->time.tmCount>0)t->time.tmCount*=-1000;    // convert ms to -µs
  209.     microSeconds+=t->time.tmCount;                    // -µs until end of interval
  210.     microSeconds-=t->stopDelay;                        // compute "cycle" time
  211.     
  212.     // Reinstall the Timer, to be ready for for another call to StartTimer()
  213.     t->time.tmWakeUp=t->time.tmReserved=t->elapsed=t->elapsedIntervals=0;                            
  214.     if(t->timeManagerVersion==gestaltRevisedTimeMgr)InsTime((QElemPtr)t);
  215.     else InsXTime((QElemPtr)t);
  216.     
  217.     return microSeconds;
  218. }
  219.  
  220. double StopTimerSecs(Timer *t)                        // Returns secs
  221. {
  222.     double s;
  223.     extern Timer defaultTimer;
  224.  
  225.     if(t==NULL)return NAN;
  226.     RmvTime((QElemPtr)t);
  227.     // add up the elapsed intervals plus the one we're in, minus the time left
  228.     t->elapsedIntervals++;
  229.     if(t->interval>0) s=t->elapsedIntervals*1000.0*t->interval;        // ms
  230.     else s=(double)-t->elapsedIntervals*t->interval;                // µs
  231.     if(t->time.tmCount>0) s-=t->time.tmCount*1000.0;    // -ms until end of interval
  232.     else s+=t->time.tmCount;                            // -µs until end of interval
  233.     s-=t->stopDelay;                                    // compute "cycle" time
  234.     s*=0.000001;
  235.     
  236.     // Reinstall the Timer, to be ready for for another call to StartTimer()
  237.     t->time.tmWakeUp=t->time.tmReserved=t->elapsed=t->elapsedIntervals=0;                            
  238.     if(t->timeManagerVersion==gestaltRevisedTimeMgr)InsTime((QElemPtr)t);
  239.     else InsXTime((QElemPtr)t);
  240.     
  241.     return s;
  242. }
  243.  
  244. // KillEveryTimer turns off all our Timers before we quit. There is no need to
  245. // free the space since the system will do that automatically.
  246.  
  247. void KillEveryTimer(void)
  248. {
  249.     Timer *t;
  250.     extern Timer qTimer;
  251.  
  252.     t=qTimer.next;
  253.     while(t!=NULL){
  254.         RmvTime((QElemPtr)t);
  255.         if(vmPresent){
  256.             UnholdMemory(t,sizeof(*t));
  257.             UnholdMemory(t->time.tmAddr,TASK_SIZE);
  258.         }
  259.         t=t->next;
  260.     }
  261. }
  262.  
  263. // The Revised & Extended Time managers set A1=&task.time before calling TimerTask
  264. // The code allowing access to globals is commented out because it is not needed here.
  265.  
  266. #pragma options(!profile)    // it would be dangerous to call the profiler from here
  267. Ptr GetA1(void)=0x2009;        // MOVE.L A1,D0
  268.  
  269. static pascal void TimerTask(void)                // Called at interrupt time
  270. {
  271.     long oldA5;
  272.     Timer *t;
  273.  
  274.     t=(Timer *)GetA1();
  275. //    oldA5 = SetA5(t->ourA5);                    // Reestablish A5 for global variables
  276.     PrimeTime((QElemPtr)t,t->interval);            // Repeat the interval
  277.     t->elapsed += t->interval;                    // Increment the time count
  278.     t->elapsedIntervals++;
  279. //    SetA5(oldA5);                                 // Restore A5
  280. }
  281.